package com.kotlin.notes.deadlock

import java.util.*
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

fun aquireLock(lock1: Lock, lock2: Lock) {
    while (true) {
        // aquire deadlock
        var gotLock1 = false
        var gotLock2 = false
        try {
            gotLock1 = lock1.tryLock()
            gotLock2 = lock2.tryLock()
        } finally {
            if (gotLock1 && gotLock2) return

            if (gotLock1) {
                lock1.unlock()
            }

            if (gotLock2) {
                lock2.unlock()
            }
        }

        // if locks not aquired
        Thread.sleep(1)
    }
}

fun solvedReentrantLock2() {
    val accountA = Account(10000, "A")
    val accountB = Account(10000, "B")

    val lockA = ReentrantLock()
    val lockB = ReentrantLock()

    val t1 = Thread {
        val rand = Random()
        for (i in 0..10) {
            aquireLock(lockA, lockB)
            try {
                Account.transfer(accountA, accountB, rand.nextInt(1000))
            } finally {
                lockA.unlock()
                lockB.unlock()
            }
        }
    }

    val t2 = Thread {
        val rand = Random()
        for (i in 0..10) {
            aquireLock(lockB, lockA)

            try {
                Account.transfer(accountB, accountA, rand.nextInt(1000))
            } finally {
                lockB.unlock()
                lockA.unlock()
            }
        }
    }

    t1.start()
    t2.start()
}


// deadlock in same order
fun solvedReentrantLock1() {
    val accountA = Account(10000, "A")
    val accountB = Account(10000, "B")

    val lock1 = ReentrantLock()
    val lock2 = ReentrantLock()

    val t1 = Thread {
        val rand = Random()
        for (i in 0..10) {

            lock1.lock()
            lock2.lock()
            try {
                Account.transfer(accountA, accountB, rand.nextInt(1000))
            } finally {
                lock1.unlock()
                lock2.unlock()
            }
        }
    }

    val t2 = Thread {
        val rand = Random()
        for (i in 0..10) {
            lock1.lock()
            lock2.lock()
            try {
                Account.transfer(accountB, accountA, rand.nextInt(1000))
            } finally {
                lock1.unlock()
                lock2.unlock()
            }
        }
    }

    t1.start()
    t2.start()
}


// requesting deadlock in same order
fun solvedSynchronized() {
    val accountA = Account(10000, "A")
    val accountB = Account(10000, "B")

    val t1 = Thread {
        val rand = Random()
        for (i in 0..10) {

            synchronized(accountA) {
                println("t1 aquired deadlock on $accountA")
                synchronized(accountB) {
                    println("t1 aquired deadlock on $accountB")
                    Account.transfer(accountA, accountB, rand.nextInt(1000))
                }
            }

        }
    }

    val t2 = Thread {
        val rand = Random()
        for (i in 0..10) {

            synchronized(accountA) {
                println("t2 aquired deadlock on $accountA")
                synchronized(accountB) {
                    println("t2 aquired deadlock on $accountB")
                    Account.transfer(accountB, accountA, rand.nextInt(1000))
                }
            }

        }

    }

    t1.start()
    t2.start()
}

fun main() {
    solvedSynchronized()
    solvedReentrantLock1()
    solvedReentrantLock2()
}